using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Xml;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Design;

namespace Stratman.DesktopProcess.Ec2Console
{
    /// <summary>
    /// Main form for the EC2 Console application; allows users to view/manage running instances,
    /// images, groups, and keypairs.
    /// </summary>
    public partial class Ec2ConsoleForm : Form
    {
        /// <summary>
        /// Web service interface to Amazon's EC2 service.
        /// </summary>
        private AmazonEC2 ec2Service = new AmazonEC2();
        /// <summary>
        /// Current list of running instances that are being displayed in the data grid.
        /// </summary>
        DescribeInstancesResponseType currentInstances = null;
        /// <summary>
        /// Current list of machine images that are being displayed in the data grid.
        /// </summary>
        DescribeImagesResponseType currentImages = null;
        /// <summary>
        /// Current list of keypairs that are being displayed in the data grid.
        /// </summary>
        DescribeKeyPairsResponseType currentKeypairs = null;
        /// <summary>
        /// Current list of groups that are being displayed in the data grid.
        /// </summary>
        DescribeSecurityGroupsResponseType currentGroups = null;
        /// <summary>
        /// Collection of flags indicating whether data grid contents are being retrieved for the
        /// various tabs in the application window.
        /// </summary>
        private Dictionary<string, bool> loadingIndicators = new Dictionary<string, bool>();
        /// <summary>
        /// Collection of callback functions that should be invoked whenever the refresh button is
        /// clicked while a particular tab is active.
        /// </summary>
        private Dictionary<string, MethodInvoker> refreshCallbacks = new Dictionary<string, MethodInvoker>();
        /// <summary>
        /// Collection of TableLayoutPanel instances representing the data grids for each tab page.
        /// </summary>
        private Dictionary<string, TableLayoutPanel> tabGrids = new Dictionary<string, TableLayoutPanel>();
        /// <summary>
        /// List containing the identifier for the selected item for each tab page.
        /// </summary>
        private Dictionary<string, string> tabSelectedItems = new Dictionary<string, string>();
        /// <summary>
        /// Collection of callback functions that should be invoked whenever a particular tab page
        /// becomes active (they are responsible for enabling/disabling toolbar items).
        /// </summary>
        private Dictionary<string, MethodInvoker> setToolbarCallbacks = new Dictionary<string, MethodInvoker>();
        
        /// <summary>
        /// Signature for a function that will add information for a machine image to the data 
        /// grid.
        /// </summary>
        /// <param name="image">
        /// Information for the image that is to be added to the data grid.
        /// </param>
        /// <param name="resultPanel">
        /// Data grid to which the images information is to be added.
        /// </param>
        private delegate void AddImageCallback(DescribeImagesResponseItemType image, TableLayoutPanel resultPanel);
        /// <summary>
        /// Signature for a funtion that will add information for a keypair to the data grid.
        /// </summary>
        /// <param name="keypair">
        /// Information for the keypair that is to be added to the data grid.
        /// </param>
        private delegate void AddKeypairCallback(DescribeKeyPairsResponseItemType keypair);
        /// <summary>
        /// Signature for a function that will add information for a group to the data grid.
        /// </summary>
        /// <param name="group">
        /// Information for the group that is to be added to the data grid.
        /// </param>
        private delegate void AddGroupCallback(SecurityGroupItemType group);
        /// <summary>
        /// Signature for a function that will add information for a currently-running instance to
        /// the data grid.
        /// </summary>
        /// <param name="instance">
        /// Information for the instance that is to be added to the data grid.
        /// </param>
        private delegate void AddInstanceCallback(RunningInstancesItemType instance);
        /// <summary>
        /// Signature for a function that will set a control's visibility to the value of a flag.
        /// </summary>
        /// <param name="control">
        /// Control whose visibility we are to set.
        /// </param>
        /// <param name="visible">
        /// Flag indicating whether or not the control should be visible.
        /// </param>
        private delegate void SetControlVisibilityCallback(Control control, bool visible);

        /// <summary>
        /// Default constructor; populates the various function callback lists and sets the Policy
        /// property of the EC2 service object.
        /// </summary>
        public Ec2ConsoleForm()
        {
            // Set the policy for the EC2 service object:  we do this because of the fact that we
            // have to provide a certificate with the request and we have to do filtering of the
            // actual body of the web service request
            ec2Service.SetPolicy((new AmazonX509Assertion()).Policy());

            // Populate the function callback lists
            refreshCallbacks["imagesTab"] = new MethodInvoker(GetImages);
            refreshCallbacks["keypairsTab"] = new MethodInvoker(GetKeypairs);
            refreshCallbacks["runningInstancesTab"] = new MethodInvoker(GetRunningInstances);
            refreshCallbacks["groupsTab"] = new MethodInvoker(GetGroups);

            setToolbarCallbacks["imagesTab"] = new MethodInvoker(ImagesTabSetToolbar);
            setToolbarCallbacks["keypairsTab"] = new MethodInvoker(KeypairsTabSetToolbar);
            setToolbarCallbacks["runningInstancesTab"] = new MethodInvoker(RunningInstancesTabSetToolbar);
            setToolbarCallbacks["groupsTab"] = new MethodInvoker(GroupsTabSetToolbar);

            InitializeComponent();
        }

        /// <summary>
        /// Starts the worker thread that will retrieve a list of machine images according to the
        /// user-defined filter criteria.
        /// </summary>
        private void GetImages()
        {
            imagesPanel.Controls.Clear();
            imagesPanel.RowCount = 1;
            tabSelectedItems.Remove("imagesTab");

            Thread getImagesThread = new Thread(new ThreadStart(GetImagesWorker));
            getImagesThread.Start();
        }

        /// <summary>
        /// Starts the worker thread that will retrieve a list of keypairs associated with the
        /// current user.
        /// </summary>
        private void GetKeypairs()
        {
            keypairsPanel.Controls.Clear();
            keypairsPanel.RowCount = 1;
            keypairDropdown.Items.Clear();

            tabSelectedItems.Remove("keypairsTab");

            Thread getKeypairsThread = new Thread(new ThreadStart(GetKeypairsWorker));
            getKeypairsThread.Start();
        }

        /// <summary>
        /// Starts the worker thread that will retrieve a list of groups associated with the
        /// current user.
        /// </summary>
        private void GetGroups()
        {
            groupsPanel.Controls.Clear();
            groupsPanel.RowCount = 1;
            groupDropdown.Items.Clear();

            tabSelectedItems.Remove("groupsTab");

            Thread getGroupsThread = new Thread(new ThreadStart(GetGroupsWorker));
            getGroupsThread.Start();
        }

        /// <summary>
        /// Starts the worker thread that will retrieve a list of instances running under the
        /// current user's context.
        /// </summary>
        private void GetRunningInstances()
        {
            runningInstancesPanel.Controls.Clear();
            runningInstancesPanel.RowCount = 1;
            tabSelectedItems.Remove("runningInstancesTab");

            Thread getRunningInstancesThread = new Thread(new ThreadStart(GetRunningInstancesWorker));
            getRunningInstancesThread.Start();
        }

        /// <summary>
        /// Worker method that retrieves the list of currently running instances using the EC2 web
        /// service interface.
        /// </summary>
        private void GetRunningInstancesWorker()
        {
            DescribeInstancesType describeInstances = new DescribeInstancesType();

            // Show the "Loading..." placeholder
            loadingIndicators["runningInstancesTab"] = true;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noRunningInstancesLabel, false);

            // Get the list of running instances
            currentInstances = ec2Service.DescribeInstances(describeInstances);

            // Iterate over each running instance and add it to the data grid
            foreach (ReservationInfoType reservation in currentInstances.reservationSet)
            {
                foreach (RunningInstancesItemType instance in reservation.instancesSet)
                {
                    if (instance.instanceState.name != "terminated")
                        Invoke(new AddInstanceCallback(AddInstance), instance);
                }
            }

            // Hide the "Loading..." placeholder
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noRunningInstancesLabel, (runningInstancesPanel.Controls.Count == 0));

            loadingIndicators["runningInstancesTab"] = false;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
        }

        /// <summary>
        /// Worker method that retrieves a list of machine images matching the filter criteria
        /// supplied by the user.
        /// </summary>
        private void GetImagesWorker()
        {
            DescribeImagesType describeImages = new DescribeImagesType();
            string[] owners = ownersTextBox.Text.Split(' ');

            // If the user specified owners of the images to be returned, populate the input object
            // appropriately
            if (ownedByMeCheckBox.Checked || !String.IsNullOrEmpty(owners[0]))
            {
                if (!String.IsNullOrEmpty(owners[0]))
                {
                    // Set the size of the owners array according to whether the user checked the
                    // "Me" checkbox
                    describeImages.ownersSet = (ownedByMeCheckBox.Checked ? new DescribeImagesOwnerType[owners.Length + 1] : new DescribeImagesOwnerType[owners.Length]);

                    // Add each owner ID specified in the text box
                    for (int i = 0; i < owners.Length; i++)
                    {
                        describeImages.ownersSet[i] = new DescribeImagesOwnerType();
                        describeImages.ownersSet[i].owner = owners[i];
                    }

                    // Add the "self" constant if necessary
                    if (ownedByMeCheckBox.Checked)
                    {
                        describeImages.ownersSet[describeImages.ownersSet.Length - 1] = new DescribeImagesOwnerType();
                        describeImages.ownersSet[describeImages.ownersSet.Length - 1].owner = "self";
                    }
                }

                // Otherwise, simply add the "self" constant to the owners array
                else
                {
                    describeImages.ownersSet = new DescribeImagesOwnerType[1];
                    describeImages.ownersSet[0] = new DescribeImagesOwnerType();
                    describeImages.ownersSet[0].owner = "self";
                }
            }

            string[] executors = executorsTextBox.Text.Split(' ');

            // If the user specified users with execution permissions on the images, populate the
            // input object appropriately
            if (executableByMeCheckBox.Checked || !String.IsNullOrEmpty(executors[0]))
            {
                if (!String.IsNullOrEmpty(executors[0]))
                {
                    // Set the size of the executors array according to whether the user checked 
                    // the "Me" checkbox
                    describeImages.executableBySet = (executableByMeCheckBox.Checked ? new DescribeImagesExecutableByType[executors.Length + 1] : new DescribeImagesExecutableByType[executors.Length]);

                    // Add each executor ID specified in the text box
                    for (int i = 0; i < executors.Length; i++)
                    {
                        describeImages.executableBySet[i] = new DescribeImagesExecutableByType();
                        describeImages.executableBySet[i].user = executors[i];
                    }
                    
                    // Add the "self" constant if necessary
                    if (executableByMeCheckBox.Checked)
                    {
                        describeImages.executableBySet[describeImages.ownersSet.Length - 1] = new DescribeImagesExecutableByType();
                        describeImages.executableBySet[describeImages.ownersSet.Length - 1].user = "self";
                    }
                }

                // Otherwise, simply add the "self" constant to the executors array
                else
                {
                    describeImages.executableBySet = new DescribeImagesExecutableByType[1];
                    describeImages.executableBySet[0] = new DescribeImagesExecutableByType();
                    describeImages.executableBySet[0].user = "self";
                }
            }

            string[] imageIDs = imageIDsTextBox.Text.Split(' ');

            // If the user specified a list of image IDs to return, populate the input object
            // appropriately
            if (!String.IsNullOrEmpty(imageIDs[0]))
            {
                describeImages.imagesSet = new DescribeImagesItemType[imageIDs.Length];

                for (int i = 0; i < imageIDs.Length; i++)
                {
                    describeImages.imagesSet[i] = new DescribeImagesItemType();
                    describeImages.imagesSet[i].imageId = imageIDs[i];
                }
            }

            // Show the "Loading..." placeholder
            loadingIndicators["imagesTab"] = true;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noImagesLabel, false);

            // Get the list of images
            currentImages = ec2Service.DescribeImages(describeImages);

            // Iterate over each image and add it to the data grid
            foreach (DescribeImagesResponseItemType image in currentImages.imagesSet)
                Invoke(new AddImageCallback(AddImage), image, imagesPanel);

            // Hide the "Loading..." placeholder
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noImagesLabel, (imagesPanel.Controls.Count == 0));

            loadingIndicators["imagesTab"] = false;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
        }

        /// <summary>
        /// Worker method that retrieves the list of keypairs belonging to the current user by
        /// making use of the EC2 web services interface.
        /// </summary>
        private void GetKeypairsWorker()
        {
            DescribeKeyPairsType describeKeypairs = new DescribeKeyPairsType();

            // Show the "Loading..." placeholder
            loadingIndicators["keypairsTab"] = true;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noKeypairsLabel, false);

            // Get the list of keypairs
            currentKeypairs = ec2Service.DescribeKeyPairs(describeKeypairs);

            // Iterate over each keypair and add it to the data grid
            foreach (DescribeKeyPairsResponseItemType keypair in currentKeypairs.keySet)
                Invoke(new AddKeypairCallback(AddKeypair), keypair);

            // Hide the "Loading..." placeholder
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noKeypairsLabel, (keypairsPanel.Controls.Count == 0));

            loadingIndicators["keypairsTab"] = false;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
        }

        /// <summary>
        /// Worker method that retrieves a list of groups belonging to the current user by making
        /// use of the EC2 web services interface.
        /// </summary>
        private void GetGroupsWorker()
        {
            DescribeSecurityGroupsType describeGroups = new DescribeSecurityGroupsType();

            // Show the "Loading..." placeholder
            loadingIndicators["groupsTab"] = true;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noGroupsLabel, false);

            // Get the list of groups
            currentGroups = ec2Service.DescribeSecurityGroups(describeGroups);

            // Iterate over each group and add it to the data grid
            foreach (SecurityGroupItemType group in currentGroups.securityGroupInfo)
                Invoke(new AddGroupCallback(AddGroup), group);

            // Hide the "Loading..." placeholder
            Invoke(new SetControlVisibilityCallback(SetControlVisibility), noGroupsLabel, (groupsPanel.Controls.Count == 0));

            loadingIndicators["groupsTab"] = false;
            Invoke(new EventHandler(ec2Tabs_SelectedIndexChanged), null, null);
        }

        /// <summary>
        /// Sets a controls visibility flag to a specified value.
        /// </summary>
        /// <param name="control">
        /// Control whose visibility flag we are to set.
        /// </param>
        /// <param name="visible">
        /// Value to which we are to set the visibility flag.
        /// </param>
        private void SetControlVisibility(Control control, bool visible)
        {
            control.Visible = visible;
        }

        /// <summary>
        /// Event handler method that is invoked when the form is loaded; populates the data grids
        /// for each tab.
        /// </summary>
        /// <param name="e">
        /// Arguments associated with this event
        /// </param>
        protected override void OnLoad(EventArgs e)
        {
            if (String.IsNullOrEmpty(Properties.Settings.Default.awsCertificate) || String.IsNullOrEmpty(Properties.Settings.Default.keypairsDirectory))
                optionsToolStripMenuItem_Click(null, null);

            // Populate the grid pointer array with the grids on each tab
            tabGrids["imagesTab"] = imagesPanel;
            tabGrids["keypairsTab"] = keypairsPanel;
            tabGrids["runningInstancesTab"] = runningInstancesPanel;
            tabGrids["groupsTab"] = groupsPanel;

            // Kick off initial data retrieval for each tab
            GetImages();
            GetKeypairs();
            GetRunningInstances();
            GetGroups();

            base.OnLoad(e);
        }

        /// <summary>
        /// Creates the necessary UI elements to add information for a security group to the data
        /// grid.
        /// </summary>
        /// <param name="group">
        /// Security group that we are to add to the data grid.
        /// </param>
        private void AddGroup(SecurityGroupItemType group)
        {
            FlowLayoutPanel panel = new FlowLayoutPanel();
            panel.FlowDirection = FlowDirection.TopDown;
            panel.Width = groupsPanel.Width;
            panel.Height = 40;
            panel.Margin = new Padding(0);
            panel.Click += new EventHandler(dataGridItem_Click);

            // Add a label for the group's name
            Label groupLabel = new Label();
            groupLabel.AutoSize = true;
            groupLabel.Text = group.groupName;
            groupLabel.Font = new Font("Tahoma", 8, FontStyle.Bold);
            groupLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(groupLabel);

            // Add a label for the group's description
            Label groupDescriptionLabel = new Label();
            groupDescriptionLabel.AutoSize = true;
            groupDescriptionLabel.Text = group.groupDescription;
            groupDescriptionLabel.Margin = new Padding(3, 2, 0, 0);
            groupDescriptionLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(groupDescriptionLabel);

            // Set the row style for the new data grid row
            if (groupsPanel.Controls.Count == 0)
                groupsPanel.RowStyles[0] = new RowStyle(SizeType.Absolute, 40);

            else
            {
                groupsPanel.RowCount++;
                groupsPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
            }

            FlowLayoutPanel containerPanel = new FlowLayoutPanel();
            containerPanel.FlowDirection = FlowDirection.LeftToRight;
            containerPanel.WrapContents = false;
            containerPanel.Width = groupsPanel.Width;
            containerPanel.Height = 40;
            containerPanel.Margin = new Padding(0);
            containerPanel.Padding = new Padding(0);
            containerPanel.BackColor = Color.White;

            // Add an icon for the group
            PictureBox groupIcon = new PictureBox();
            groupIcon.Image = Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("Stratman.DesktopProcess.Ec2Console.Images.group.png"));
            groupIcon.Width = 32;
            groupIcon.Height = 32;
            groupIcon.Click += new EventHandler(dataGridItem_Click);

            // Add all of the UI elements to the container control
            containerPanel.Controls.Add(groupIcon);
            containerPanel.Controls.Add(panel);
            containerPanel.Name = "containerPanel_" + group.groupName;
            containerPanel.Click += new EventHandler(dataGridItem_Click);

            groupsPanel.Controls.Add(containerPanel);
            groupsPanel.SetRow(containerPanel, groupsPanel.RowCount);

            groupDropdown.Items.Add(group.groupName);
        }

        /// <summary>
        /// Creates the necessary UI elements to add information for a keypair to the data grid.
        /// </summary>
        /// <param name="keypair">
        /// Keypair that we are to add to the data grid.
        /// </param>
        private void AddKeypair(DescribeKeyPairsResponseItemType keypair)
        {
            FlowLayoutPanel panel = new FlowLayoutPanel();
            panel.FlowDirection = FlowDirection.TopDown;
            panel.Width = keypairsPanel.Width;
            panel.Height = 40;
            panel.Margin = new Padding(0);
            panel.Click += new EventHandler(dataGridItem_Click);

            // Add a label for the keypair's name
            Label keypairLabel = new Label();
            keypairLabel.AutoSize = true;
            keypairLabel.Text = keypair.keyName;
            keypairLabel.Font = new Font("Tahoma", 8, FontStyle.Bold);
            keypairLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(keypairLabel);

            // Add a label for the keypair's thumbprint
            Label keypairThumbrintLabel = new Label();
            keypairThumbrintLabel.AutoSize = true;
            keypairThumbrintLabel.Text = keypair.keyFingerprint;
            keypairThumbrintLabel.Margin = new Padding(3, 2, 0, 0);
            keypairThumbrintLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(keypairThumbrintLabel);

            // Set the row style for the new data grid row
            if (keypairsPanel.Controls.Count == 0)
                keypairsPanel.RowStyles[0] = new RowStyle(SizeType.Absolute, 40);

            else
            {
                keypairsPanel.RowCount++;
                keypairsPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
            }

            FlowLayoutPanel containerPanel = new FlowLayoutPanel();
            containerPanel.FlowDirection = FlowDirection.LeftToRight;
            containerPanel.WrapContents = false;
            containerPanel.Width = keypairsPanel.Width;
            containerPanel.Height = 40;
            containerPanel.Margin = new Padding(0);
            containerPanel.Padding = new Padding(0);
            containerPanel.BackColor = Color.White;

            // Add an icon for the keypair
            PictureBox keypairIcon = new PictureBox();
            keypairIcon.Image = Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("Stratman.DesktopProcess.Ec2Console.Images.keypair.png"));
            keypairIcon.Width = 32;
            keypairIcon.Height = 32;
            keypairIcon.Click += new EventHandler(dataGridItem_Click);

            // Add all of the UI elements to the container control
            containerPanel.Controls.Add(keypairIcon);
            containerPanel.Controls.Add(panel);
            containerPanel.Name = "containerPanel_" + keypair.keyName;
            containerPanel.Click += new EventHandler(dataGridItem_Click);

            keypairsPanel.Controls.Add(containerPanel);
            keypairsPanel.SetRow(containerPanel, keypairsPanel.RowCount);

            keypairDropdown.Items.Add(keypair.keyName);
        }

        /// <summary>
        /// Event handler that is invoked whenever a data grid row is clicked; sets the background
        /// color of the row appropriately and the selected item identifier for the current tab.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with the event.
        /// </param>
        void dataGridItem_Click(object sender, EventArgs e)
        {
            // Reset the background color for each row
            foreach (FlowLayoutPanel row in tabGrids[ec2Tabs.SelectedTab.Name].Controls)
                row.BackColor = Color.White;

            // Trace up the control hierarchy until you find a control whose name starts with
            // "containerPanel_"
            Control selectedItem = (Control)sender;

            while (!(selectedItem is FlowLayoutPanel) || !selectedItem.Name.StartsWith("containerPanel_"))
                selectedItem = selectedItem.Parent;

            // Set the row's background color and set the selected item identifier for the current
            // tab
            ((FlowLayoutPanel)selectedItem).BackColor = Color.FromArgb(183, 196, 255);
            tabSelectedItems[ec2Tabs.SelectedTab.Name] = selectedItem.Name.Substring(15);

            // Call the necessary function to enable/disable the various toolbar icons
            Invoke(setToolbarCallbacks[ec2Tabs.SelectedTab.Name]);
        }

        /// <summary>
        /// Creates the necessary UI elements to add information for a machine image to the data
        /// grid.
        /// </summary>
        /// <param name="image">
        /// Machine image that we are to add to the data grid.
        /// </param>
        /// <param name="resultPanel">
        /// Data grid that we are to add the information to.
        /// </param>
        private void AddImage(DescribeImagesResponseItemType image, TableLayoutPanel resultPanel)
        {
            FlowLayoutPanel panel = new FlowLayoutPanel();
            panel.FlowDirection = FlowDirection.TopDown;
            panel.Width = resultPanel.Width;
            panel.Height = 45;
            panel.Margin = new Padding(0);
            panel.Click += new EventHandler(dataGridItem_Click);

            // Add a label for the image's identifier and an indicator as to whether it's public or
            // private
            Label imageLabel = new Label();
            imageLabel.AutoSize = true;
            imageLabel.Text = image.imageId + " (" + (image.isPublic ? "public" : "private") + ", " + image.imageState + ")";
            imageLabel.Font = new Font("Tahoma", 8, FontStyle.Bold);
            imageLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(imageLabel);

            // Add a link to the S3 location for the image's manifest
            LinkLabel imageLinkLabel = new LinkLabel();
            imageLinkLabel.Links.Add(0, image.imageLocation.Length + 1, "https://s3.amazonaws.com/" + image.imageLocation);
            imageLinkLabel.AutoSize = true;
            imageLinkLabel.Text = "/" + image.imageLocation;
            imageLinkLabel.LinkClicked += new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
            imageLinkLabel.Margin = new Padding(3, 2, 0, 0);
            panel.Controls.Add(imageLinkLabel);

            // Add a label for the image's owner
            Label ownerLabel = new Label();
            ownerLabel.AutoSize = true;
            ownerLabel.Text = "Owned by user ID " + image.imageOwnerId;
            ownerLabel.Click += new EventHandler(dataGridItem_Click);
            ownerLabel.Margin = new Padding(3, 2, 0, 0);
            panel.Controls.Add(ownerLabel);

            // Set the row style for the new data grid row
            if (resultPanel.Controls.Count == 0)
                resultPanel.RowStyles[0] = new RowStyle(SizeType.Absolute, 45);

            else
            {
                resultPanel.RowCount++;
                resultPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 45));
            }

            FlowLayoutPanel containerPanel = new FlowLayoutPanel();
            containerPanel.FlowDirection = FlowDirection.LeftToRight;
            containerPanel.WrapContents = false;
            containerPanel.Width = resultPanel.Width;
            containerPanel.Height = 45;
            containerPanel.Margin = new Padding(0);
            containerPanel.Padding = new Padding(0);
            containerPanel.BackColor = Color.White;
            containerPanel.Name = "containerPanel_" + image.imageId;

            // Add an icon for the image (varies depending on whether this is a machine, a kernel,
            // or a ramdisk image
            PictureBox imageIcon = new PictureBox();
            imageIcon.Image = Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("Stratman.DesktopProcess.Ec2Console.Images." + image.imageType + ".png"));
            imageIcon.Width = 32;
            imageIcon.Height = 32;
            imageIcon.Click += new EventHandler(dataGridItem_Click);

            // Add all of the UI elements to the container control
            containerPanel.Controls.Add(imageIcon);
            containerPanel.Controls.Add(panel);
            containerPanel.Click += new EventHandler(dataGridItem_Click);

            resultPanel.Controls.Add(containerPanel);
            resultPanel.SetRow(containerPanel, resultPanel.RowCount);
        }

        /// <summary>
        /// Creates the necessary UI elements to add information for a running machine instance to
        /// the data grid.
        /// </summary>
        /// <param name="instance">
        /// Running instance that we are to add to the data grid.
        /// </param>
        void AddInstance(RunningInstancesItemType instance)
        {
            FlowLayoutPanel panel = new FlowLayoutPanel();
            panel.FlowDirection = FlowDirection.TopDown;
            panel.Width = runningInstancesPanel.Width;
            panel.Height = 48;
            panel.Margin = new Padding(0);
            panel.Click += new EventHandler(dataGridItem_Click);

            // Add a label for the instance's identifier and its state
            Label instanceLabel = new Label();
            instanceLabel.AutoSize = true;
            instanceLabel.Text = instance.instanceId + " (" + instance.instanceState.name.Replace("-", " ") + ")";
            instanceLabel.Font = new Font("Tahoma", 8, FontStyle.Bold);
            instanceLabel.Click += new EventHandler(dataGridItem_Click);
            panel.Controls.Add(instanceLabel);

            // Add a label for the image identifier off of which this instance is running
            Label instanceIDLabel = new Label();
            instanceIDLabel.AutoSize = true;
            instanceIDLabel.Text = "Using image ID " + instance.imageId;
            instanceIDLabel.Click += new EventHandler(dataGridItem_Click);
            instanceIDLabel.Margin = new Padding(2, 2, 0, 0);
            panel.Controls.Add(instanceIDLabel);

            // If the instance is actually running, show a number of other useful pices of 
            // information
            if (instance.instanceState.name == "running")
            {
                // Add an HTTP link to the instances DNS name
                LinkLabel instanceLinkLabel = new LinkLabel();
                instanceLinkLabel.Links.Add(0, instance.dnsName.Length + 8, "http://" + instance.dnsName);
                instanceLinkLabel.AutoSize = true;
                instanceLinkLabel.Text = instance.dnsName;
                instanceLinkLabel.LinkClicked += new LinkLabelLinkClickedEventHandler(linkLabel_LinkClicked);
                instanceLinkLabel.Margin = new Padding(3, 2, 0, 0);
                instanceLinkLabel.Padding = new Padding(0);
                panel.Controls.Add(instanceLinkLabel);

                TimeSpan uptime = new TimeSpan(DateTime.Now.Ticks - instance.launchTime.ToLocalTime().Ticks);

                // Add a label indicating how long the instance has been running
                Label uptimeLabel = new Label();
                uptimeLabel.AutoSize = true;
                uptimeLabel.Text = "Up since " + instance.launchTime.ToLocalTime().ToString("MMMM d, yyyy hh:mm:ss tt") + " (" + uptime.Days + " days, " + String.Format("{0:00}", uptime.Hours) + ":" + String.Format("{0:00}", uptime.Minutes) + ":" + String.Format("{0:00}", uptime.Seconds) + ")";
                uptimeLabel.Click += new EventHandler(dataGridItem_Click);
                panel.Controls.Add(uptimeLabel);

                // If the instance was started with a keypair, add a label for the keypair's
                // identifier
                if (!String.IsNullOrEmpty(instance.keyName))
                {
                    Label keyLabel = new Label();
                    keyLabel.AutoSize = true;
                    keyLabel.Text = "Using key " + instance.keyName;
                    keyLabel.Click += new EventHandler(dataGridItem_Click);
                    keyLabel.Margin = new Padding(3, 2, 0, 0);
                    panel.Controls.Add(keyLabel);
                }
            }

            // Set the row style for the new data grid row
            if (runningInstancesPanel.Controls.Count == 0)
                runningInstancesPanel.RowStyles[0] = new RowStyle(SizeType.Absolute, 48);

            else
            {
                runningInstancesPanel.RowCount++;
                runningInstancesPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 48));
            }

            FlowLayoutPanel containerPanel = new FlowLayoutPanel();
            containerPanel.FlowDirection = FlowDirection.LeftToRight;
            containerPanel.WrapContents = false;
            containerPanel.Width = runningInstancesPanel.Width;
            containerPanel.Height = 48;
            containerPanel.Margin = new Padding(0);
            containerPanel.Padding = new Padding(0);
            containerPanel.BackColor = Color.White;
            containerPanel.Name = "containerPanel_" + instance.instanceId;

            // Add an icon for the instance
            PictureBox imageIcon = new PictureBox();
            imageIcon.Image = Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("Stratman.DesktopProcess.Ec2Console.Images.machine.png"));
            imageIcon.Width = 32;
            imageIcon.Height = 32;
            imageIcon.Click += new EventHandler(dataGridItem_Click);

            // Add all of the UI elements to the container control
            containerPanel.Controls.Add(imageIcon);
            containerPanel.Controls.Add(panel);
            containerPanel.Click += new EventHandler(dataGridItem_Click);

            runningInstancesPanel.Controls.Add(containerPanel);
            runningInstancesPanel.SetRow(containerPanel, runningInstancesPanel.RowCount);
        }

        /// <summary>
        /// Enables/disables the various toolbar icons when the images tab is selected.
        /// </summary>
        void ImagesTabSetToolbar()
        {
            bool enabledFlag = (tabSelectedItems.ContainsKey("imagesTab") && !String.IsNullOrEmpty(tabSelectedItems["imagesTab"]));
            bool amiSelectedFlag = false;

            if (enabledFlag)
            {
                foreach (DescribeImagesResponseItemType image in currentImages.imagesSet)
                {
                    // Set the AMI selected flag to true only when a machine (as opposed to a 
                    // kernel or ramdisk) image is selected
                    if (image.imageId == tabSelectedItems["imagesTab"])
                        amiSelectedFlag = (image.imageType == "machine");
                }
            }

            startInstanceButton.Enabled = amiSelectedFlag;
            instanceCountTextBox.Enabled = amiSelectedFlag;
            keypairDropdown.Enabled = amiSelectedFlag;
            propertiesButton.Enabled = enabledFlag;
            accessViaDropdownButton.Enabled = false;
            terminateInstanceButton.Enabled = false;
            deleteButton.Enabled = enabledFlag;
            groupDropdown.Enabled = amiSelectedFlag;
        }

        /// <summary>
        /// Enables/disables the various toolbar icons when the groups tab is selected.
        /// </summary>
        void GroupsTabSetToolbar()
        {
            bool enabledFlag = (tabSelectedItems.ContainsKey("groupsTab") && !String.IsNullOrEmpty(tabSelectedItems["groupsTab"]));

            startInstanceButton.Enabled = false;
            instanceCountTextBox.Enabled = false;
            keypairDropdown.Enabled = false;
            propertiesButton.Enabled = enabledFlag;
            accessViaDropdownButton.Enabled = false;
            terminateInstanceButton.Enabled = false;
            deleteButton.Enabled = enabledFlag;
            groupDropdown.Enabled = false;
        }

        /// <summary>
        /// Enables/disables the various toolbar icons when the keypairs tab is selected.
        /// </summary>
        void KeypairsTabSetToolbar()
        {
            bool enabledFlag = (tabSelectedItems.ContainsKey("keypairsTab") && !String.IsNullOrEmpty(tabSelectedItems["keypairsTab"]));

            startInstanceButton.Enabled = false;
            instanceCountTextBox.Enabled = false;
            keypairDropdown.Enabled = false;
            propertiesButton.Enabled = false;
            accessViaDropdownButton.Enabled = false;
            terminateInstanceButton.Enabled = false;
            deleteButton.Enabled = enabledFlag;
            groupDropdown.Enabled = false;
        }

        /// <summary>
        /// Enables/disables the various toolbar icons when the running instances tab is selected.
        /// </summary>
        void RunningInstancesTabSetToolbar()
        {
            bool enabledFlag = (tabSelectedItems.ContainsKey("runningInstancesTab") && !String.IsNullOrEmpty(tabSelectedItems["runningInstancesTab"]));

            terminateInstanceButton.Enabled = enabledFlag;
            startInstanceButton.Enabled = false;
            instanceCountTextBox.Enabled = false;
            keypairDropdown.Enabled = false;
            propertiesButton.Enabled = enabledFlag;
            accessViaDropdownButton.Enabled = enabledFlag;
            deleteButton.Enabled = false;
            groupDropdown.Enabled = false;
        }

        /// <summary>
        /// Event handler that is invoked whenever a link label is clicked; starts whichever
        /// application is associated with the particular protocol.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with the event.
        /// </param>
        private void linkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Process.Start((string)e.Link.LinkData);
        }

        /// <summary>
        /// Event handler that is invoked whenever the selected tab is changed in the main tab set;
        /// sets the visibility of the tab's data grid and the loading label and invokes the
        /// necessary toolbar function to enable/disable the toolbar icons.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with the event.
        /// </param>
        private void ec2Tabs_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Set the visibility of the loading label depending on the value of the loading 
            // indicator for this tab
            loadingLabel.Visible = (loadingIndicators.ContainsKey(ec2Tabs.SelectedTab.Name) ? loadingIndicators[ec2Tabs.SelectedTab.Name] : false);

            // Set the visibility of the data grid for this tab depending on whether we are still
            // loading data for it or not
            if (tabGrids.ContainsKey(ec2Tabs.SelectedTab.Name))
                tabGrids[ec2Tabs.SelectedTab.Name].Visible = (loadingIndicators.ContainsKey(ec2Tabs.SelectedTab.Name) ? !loadingIndicators[ec2Tabs.SelectedTab.Name] && tabGrids[ec2Tabs.SelectedTab.Name].Controls.Count > 0 : false);

            // Invoke the toolbar function for this tab to enable/disable the toolbar icons
            if (setToolbarCallbacks.ContainsKey(ec2Tabs.SelectedTab.Name))
                Invoke(new MethodInvoker(setToolbarCallbacks[ec2Tabs.SelectedTab.Name]));
        }

        /// <summary>
        /// Event handler that is invoked whenever the "About" menu item is clicked.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// Event handler that is invoked whenever the refresh button is clicked; invokes the
        /// refresh function for the current tab.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void refreshButton_Click(object sender, EventArgs e)
        {
            // Invoke the refresh function for the current tab
            if (refreshCallbacks.ContainsKey(ec2Tabs.SelectedTab.Name))
                Invoke(new MethodInvoker(refreshCallbacks[ec2Tabs.SelectedTab.Name]));
        }

        /// <summary>
        /// Event handler that is invoked whenever the start instance button is clicked; sends a
        /// "run instance" request through the EC2 web service interface with the necessary
        /// parameters.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void startInstanceButton_Click(object sender, EventArgs e)
        {
            RunInstancesType runInstances = new RunInstancesType();

            runInstances.imageId = tabSelectedItems["imagesTab"];

            // Set the keypair parameter for the run instance request if a keypair was selected
            if (keypairDropdown.Text != "Keypair...")
                runInstances.keyName = keypairDropdown.Text;

            // Set the security group parameter for the run instance request if a group was 
            // selected
            if (groupDropdown.Text != "Group...")
            {
                runInstances.groupSet = new GroupItemType[1];
                runInstances.groupSet[0] = new GroupItemType();
                runInstances.groupSet[0].groupId = groupDropdown.Text;
            }

            // Define the number of instances to start
            runInstances.minCount = (instanceCountTextBox.Text != "Instance count" ? Convert.ToInt32(instanceCountTextBox.Text) : 1);
            runInstances.maxCount = (instanceCountTextBox.Text != "Instance count" ? Convert.ToInt32(instanceCountTextBox.Text) : 1);

            // Send the run instance request
            ec2Service.RunInstances(runInstances);
        }

        /// <summary>
        /// Event handler that is invoked whenever the "Options" menu item is clicked; displays the
        /// options dialog and then saves the values provided by the user.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Create the options form object and populate the various inputs
            OptionsForm optionsForm = new OptionsForm();
            optionsForm.AwsCertificate = Properties.Settings.Default.awsCertificate;
            optionsForm.KeypairsDirectory = Properties.Settings.Default.keypairsDirectory;
            optionsForm.PathToSSH = Properties.Settings.Default.pathToSSH;
            optionsForm.PathToSFTP = Properties.Settings.Default.pathToSFTP;

            // If the user clicked the OK button, save the settings that they entered
            if (optionsForm.ShowDialog() == DialogResult.OK)
            {
                Properties.Settings.Default.awsCertificate = optionsForm.AwsCertificate;
                Properties.Settings.Default.keypairsDirectory = optionsForm.KeypairsDirectory;
                Properties.Settings.Default.pathToSSH = optionsForm.PathToSSH;
                Properties.Settings.Default.pathToSFTP = optionsForm.PathToSFTP;
                Properties.Settings.Default.Save();

                // Reset the policy object for the EC2 service object in case they selected another
                // certificate to use for requests
                ec2Service.SetPolicy((new AmazonX509Assertion()).Policy());
            }

            optionsForm.Dispose();
        }

        /// <summary>
        /// Event handler that is invoked whenever the terminate instance button is clicked; sends
        /// a "terminate instance" request through the EC2 web service interface.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void terminateInstanceButton_Click(object sender, EventArgs e)
        {
            // Display the confirmation message
            if (MessageBox.Show("Are you sure that you want to terminate this instance?", "Terminate instance?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                return;

            TerminateInstancesType terminateInstances = new TerminateInstancesType();

            terminateInstances.instancesSet = new TerminateInstancesItemType[1];
            terminateInstances.instancesSet[0] = new TerminateInstancesItemType();
            terminateInstances.instancesSet[0].instanceId = tabSelectedItems["runningInstancesTab"];

            // Send the terminate instance request and refresh the data grid
            ec2Service.TerminateInstances(terminateInstances);
            refreshButton_Click(null, null);
        }

        /// <summary>
        /// Finds an instance object matching a particular identifier in a list of running 
        /// instances.
        /// </summary>
        /// <param name="instanceID">
        /// Instance identifier that we are to search for.
        /// </param>
        /// <param name="instances">
        /// List of instances to search through.
        /// </param>
        /// <returns>
        /// The matching instance object, if one exists, null otherwise.
        /// </returns>
        private RunningInstancesItemType FindInstance(string instanceID, DescribeInstancesResponseType instances)
        {
            foreach (ReservationInfoType reservation in instances.reservationSet)
            {
                foreach (RunningInstancesItemType instance in reservation.instancesSet)
                {
                    if (instance.instanceId == instanceID)
                        return instance;
                }
            }

            return null;
        }

        /// <summary>
        /// Event handler that is invoked when someone selects the HTTP option under the "Access
        /// via..." menu; starts up a browser session pointing to the instance's DNS name.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void httpMenuOption_Click(object sender, EventArgs e)
        {
            RunningInstancesItemType instance = FindInstance(tabSelectedItems["runningInstancesTab"], currentInstances);
            Process.Start("http://" + instance.dnsName);
        }

        /// <summary>
        /// Event handler that is invoked when someone selects the HTTPS option under the "Access
        /// via..." menu; starts up a browser session pointing to the instance's DNS name.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void httpsMenuOption_Click(object sender, EventArgs e)
        {
            RunningInstancesItemType instance = FindInstance(tabSelectedItems["runningInstancesTab"], currentInstances);
            Process.Start("https://" + instance.dnsName);
        }

        /// <summary>
        /// Event handler that is invoked when someone selects the SSH option under the "Access
        /// via..." menu; starts up Putty session pointing to the instance's DNS name.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void sshMenuOption_Click(object sender, EventArgs e)
        {
            RunningInstancesItemType instance = FindInstance(tabSelectedItems["runningInstancesTab"], currentInstances);
            string keyFile = null;

            // Get the file associated with the keypair that the user selected
            try
            {
                keyFile = GetKeyFile(instance.keyName);
            }

            catch (FileNotFoundException exception)
            {
                MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // Start Putty with the necessary parameters
            Process.Start(Properties.Settings.Default.pathToSSH, (String.IsNullOrEmpty(keyFile) ? "" : "-i \"" + keyFile + "\" ") + "root@" + instance.dnsName);
        }

        /// <summary>
        /// Gets a Putty-comaptible key file associated with the keypair that the user has 
        /// selected.
        /// </summary>
        /// <param name="keyName">
        /// Name of the keypair that the user selected.
        /// </param>
        /// <returns>
        /// Path to the Putty key file.
        /// </returns>
        private string GetKeyFile(string keyName)
        {
            if (String.IsNullOrEmpty(keyName))
                return null;

            // First, look for a file called [keypair name].ppk; if one exists, then we know that
            // we've already created a Putty key file for this keypair, so just return that file
            // path
            if (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\" + keyName + ".ppk"))
                return Properties.Settings.Default.keypairsDirectory + "\\" + keyName + ".ppk";

            // Next, look for a file called id_rsa-[keypair name] or just [keypair name]; if one 
            // exists, then we have an OpenSSH key file for this keypair and need to convert it to 
            // Putty's format (OpenSSH keys aren't necessarily tamper-proof, which is why Putty 
            // won't use them without a conversion operation)
            else if (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\id_rsa-" + keyName) || File.Exists(Properties.Settings.Default.keypairsDirectory + "\\" + keyName))
            {
                // Run puttygen.exe to convert the key file
                string fileName = (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\id_rsa-" + keyName) ? Properties.Settings.Default.keypairsDirectory + "\\id_rsa-" + keyName : Properties.Settings.Default.keypairsDirectory + "\\" + keyName);

                Process puttygenProcess = Process.Start("Tools\\puttygen.exe", "\"" + fileName + "\" -export \"" + Properties.Settings.Default.keypairsDirectory + "\\" + keyName + ".ppk\"");
                puttygenProcess.WaitForExit();

                return Properties.Settings.Default.keypairsDirectory + "\\" + keyName + ".ppk";
            }

            // Otherwise, we have no key files associated with that keypair, so throw an exception
            throw new FileNotFoundException("Unable to find the key file for " + keyName + ".");
        }

        /// <summary>
        /// Event handler that is invoked when someone selects the SFTP option under the "Access
        /// via..." menu; starts up WinScp session pointing to the instance's DNS name.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void sftpMenuOption_Click(object sender, EventArgs e)
        {
            RunningInstancesItemType instance = FindInstance(tabSelectedItems["runningInstancesTab"], currentInstances);
            string keyFile = null;

            // Get the file associated with the keypair that the user selected
            try
            {
                keyFile = GetKeyFile(instance.keyName);
            }

            catch (FileNotFoundException exception)
            {
                MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // Start WinScp with the necessary parameters
            Process.Start(Properties.Settings.Default.pathToSFTP, "sftp://root@" + instance.dnsName + (String.IsNullOrEmpty(keyFile) ? "" : " /privatekey:\"" + keyFile + "\""));
        }

        /// <summary>
        /// Event handler that is invoked when the delete button is clicked; performs whatever EC2
        /// web service calls are necessary depending on which tab is selected.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void deleteButton_Click(object sender, EventArgs e)
        {
            if (ec2Tabs.SelectedTab.Name == "keypairsTab")
            {
                // Show a confirmation message
                if (MessageBox.Show("Are you sure that you want to delete this keypair?", "Delete keypair?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                    return;

                // Send a delete keypair request to the EC2 web service interface
                DeleteKeyPairType deleteKeypair = new DeleteKeyPairType();
                deleteKeypair.keyName = tabSelectedItems["keypairsTab"];

                ec2Service.DeleteKeyPair(deleteKeypair);

                // Delete any key files on the disk
                if (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\" + tabSelectedItems["keypairsTab"]))
                    File.Delete(Properties.Settings.Default.keypairsDirectory + "\\" + tabSelectedItems["keypairsTab"]);

                else if (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\id_rsa-" + tabSelectedItems["keypairsTab"]))
                    File.Delete(Properties.Settings.Default.keypairsDirectory + "\\id_rsa-" + tabSelectedItems["keypairsTab"]);

                if (File.Exists(Properties.Settings.Default.keypairsDirectory + "\\" + tabSelectedItems["keypairsTab"] + ".ppk"))
                    File.Delete(Properties.Settings.Default.keypairsDirectory + "\\" + tabSelectedItems["keypairsTab"] + ".ppk");

                // Refresh the data grid
                refreshButton_Click(null, null);
            }

            else if (ec2Tabs.SelectedTab.Name == "groupsTab")
            {
                // Show a confirmation message
                if (MessageBox.Show("Are you sure that you want to delete this group?", "Delete group?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                    return;

                // Send a delete group request to the EC2 web service interface and refresh the 
                // data grid
                DeleteSecurityGroupType deleteGroup = new DeleteSecurityGroupType();
                deleteGroup.groupName = tabSelectedItems["groupsTab"];

                ec2Service.DeleteSecurityGroup(deleteGroup);
                refreshButton_Click(null, null);
            }
        }

        /// <summary>
        /// Event handler that is invoked whenever the "New keypair..." menu option is clicked;
        /// collects the necessary inputs from the user, makes a create keypair web service call,
        /// and then saves the key file to disk.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void keypairToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show the form instance to gather the necessary inputs from the user
            CreateKeypairForm createKeypairForm = new CreateKeypairForm();

            if (createKeypairForm.ShowDialog() == DialogResult.OK)
            {
                CreateKeyPairType createKeypair = new CreateKeyPairType();
                createKeypair.keyName = createKeypairForm.KeypairName;

                // Make the create keypair request to the EC2 web service interface
                CreateKeyPairResponseType response = ec2Service.CreateKeyPair(createKeypair);
                StreamWriter keyFile = File.CreateText(Properties.Settings.Default.keypairsDirectory + "\\" + createKeypair.keyName);

                // Save the key file to disk
                keyFile.Write(response.keyMaterial);
                keyFile.Close();

                // Refresh the list of keypairs
                GetKeypairs();
            }

            createKeypairForm.Dispose();
        }

        /// <summary>
        /// Event handler that is invoked when the "Exit" menu item is clicked; closes the 
        /// application.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        /// <summary>
        /// Event handler that is invoked when the "Authorizations" menu item is clicked; shows a
        /// form instance allowing the user to add/modify authorizations and then saves those
        /// changes back to the server.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void authorizationsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Get the current list of security groups and then show the form allowing the user to
            // change the authorizations associated with those security groups
            DescribeSecurityGroupsType describeSecurityGroups = new DescribeSecurityGroupsType();
            DescribeSecurityGroupsResponseType response = ec2Service.DescribeSecurityGroups(describeSecurityGroups);
            AuthorizationsForm authorizationsForm = new AuthorizationsForm(response);
            
            if (authorizationsForm.ShowDialog() == DialogResult.OK)
            {
                // Revoke all of the current authorizations
                foreach (SecurityGroupItemType securityGroup in response.securityGroupInfo)
                {
                    RevokeSecurityGroupIngressType revocation = new RevokeSecurityGroupIngressType();
                    
                    revocation.groupName = securityGroup.groupName;
                    revocation.ipPermissions = new IpPermissionType[securityGroup.ipPermissions.Length];
                    Array.Copy(securityGroup.ipPermissions, revocation.ipPermissions, revocation.ipPermissions.Length);

                    ec2Service.RevokeSecurityGroupIngress(revocation);
                }

                // Save the current list of authorizations to the server
                foreach (IpPermissionType authorization in authorizationsForm.Authorizations)
                {
                    foreach (string securityGroup in authorizationsForm.AuthorizationMappings[authorization])
                    {
                        AuthorizeSecurityGroupIngressType permission = new AuthorizeSecurityGroupIngressType();

                        permission.groupName = securityGroup;
                        permission.ipPermissions = new IpPermissionType[1];
                        permission.ipPermissions[0] = authorization;

                        ec2Service.AuthorizeSecurityGroupIngress(permission);
                    }
                }
            }

            authorizationsForm.Dispose();
        }

        /// <summary>
        /// Event handler that is invoked whenever the "New group..." menu option is clicked;
        /// collects the necessary inputs from the user and makes a create group web service call.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void groupToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Show the form instance to gather the necessary inputs from the user
            CreateGroupForm createGroupForm = new CreateGroupForm();

            if (createGroupForm.ShowDialog() == DialogResult.OK)
            {
                CreateSecurityGroupType createGroup = new CreateSecurityGroupType();
                createGroup.groupName = createGroupForm.GroupName;
                createGroup.groupDescription = createGroupForm.GroupDescription;

                // Make the create group request to the EC2 web service interface and refresh the
                // list of groups
                ec2Service.CreateSecurityGroup(createGroup);
                GetGroups();
            }

            createGroupForm.Dispose();
        }

        /// <summary>
        /// Event handler that is invoked whenever the OK button is clicked in the filter section
        /// of the images tab; refreshes the list of images.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void okFilterButton_Click(object sender, EventArgs e)
        {
            refreshButton_Click(null, null);
        }

        /// <summary>
        /// Event handler that is invoked whenever the reset button is clicked in the filter 
        /// section of the images tab; blanks out the values for all of the filter fields.
        /// </summary>
        /// <param name="sender">
        /// Object from which this event originated.
        /// </param>
        /// <param name="e">
        /// Arguments associated with this event.
        /// </param>
        private void resetFilterButton_Click(object sender, EventArgs e)
        {
            ownedByMeCheckBox.Checked = false;
            ownersTextBox.Text = "";
            executableByMeCheckBox.Checked = false;
            executorsTextBox.Text = "";
            imageIDsTextBox.Text = "";
        }

        /// <summary>
        /// Custom certificate assertion class used with web service calls to Amazon's EC2 service.
        /// This class is used to set the client certificate used in the request as well as to
        /// filter out elements in the request (specifically the web service addressing nodes) that
        /// Amazon can't deal with.
        /// </summary>
        private class AmazonX509Assertion : MutualCertificate10Assertion
        {
            /// <summary>
            /// The name of the certificate to use in the web service request; is set to the value
            /// that the user specifies in the options dialog.
            /// </summary>
            private static string awsCertificate = Properties.Settings.Default.awsCertificate;

            /// <summary>
            /// Default constructor; sets the token providers for the request and parameters
            /// associated with the request.
            /// </summary>
            public AmazonX509Assertion()
            {
                ClientX509TokenProvider = new X509TokenProvider(StoreLocation.CurrentUser, StoreName.My, awsCertificate, X509FindType.FindBySubjectName);
                ServiceX509TokenProvider = new X509TokenProvider(StoreLocation.CurrentUser, StoreName.My, awsCertificate, X509FindType.FindBySubjectName);

                Protection.Request.EncryptBody = false;
                Protection.Response.EncryptBody = false;

                Protection.Request.SignatureOptions = SignatureOptions.IncludeTimestamp | SignatureOptions.IncludeSoapBody;
            }

            /// <summary>
            /// Creates the Policy object used in the request.
            /// </summary>
            /// <returns>
            /// The Policy object used in the request.
            /// </returns>
            public Policy Policy()
            {
                Policy policy = new Policy();
                policy.Assertions.Add(this);

                return policy;
            }

            /// <summary>
            /// Creates the SoapFilter used to filter data being returned from the server; we
            /// return null in this case since we don't need to do any inbound filtering.
            /// </summary>
            /// <param name="context">
            /// Filter context to use during the creation process.
            /// </param>
            /// <returns>
            /// Null in all cases.
            /// </returns>
            public override SoapFilter CreateClientInputFilter(FilterCreationContext context)
            {
                return null;
            }

            /// <summary>
            /// Creates the SoapFilter used to filter data being sent to the server; we set this
            /// to a custom filtering class that removes that web service addressing nodes that
            /// Amazon can't deal with.
            /// </summary>
            /// <param name="context">
            /// Filter context to use during the creation process.
            /// </param>
            /// <returns>
            /// Our custom SoapFilter object to remove the web service addressing nodes.
            /// </returns>
            public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)
            {
                return new AmazonSoapFilter(base.CreateClientOutputFilter(context));
            }
        }

        /// <summary>
        /// Custom SoapFilter class that removes web service addressing nodes from outgoing SOAP
        /// requests so that Amazon's servers don't throw errors.
        /// </summary>
        private class AmazonSoapFilter : SoapFilter
        {
            /// <summary>
            /// "Base" filter that we are to call during the ProcessMessage() call.
            /// </summary>
            private SoapFilter filter = null;

            /// <summary>
            /// Default constructor; sets the base filter member variable.
            /// </summary>
            /// <param name="filter">
            /// "Base" filter that we are to call during the ProcessMessage() call.
            /// </param>
            public AmazonSoapFilter(SoapFilter filter)
                : base()
            {
                this.filter = filter;
            }

            /// <summary>
            /// Filters out the web service addressing nodes from the SOAP data.
            /// </summary>
            /// <param name="envelope">
            /// SOAP envelope that we are to filter.
            /// </param>
            /// <returns>
            /// SoapFilterResult.Continue in all cases.
            /// </returns>
            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                // First call the "base" filter
                filter.ProcessMessage(envelope);

                // Get all web service addressing nodes in the SOAP data
                XmlNamespaceManager nsManager = new XmlNamespaceManager(envelope.NameTable);
                nsManager.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
                nsManager.AddNamespace("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");

                XmlNodeList nodes = envelope.SelectNodes("/soap:Envelope/soap:Header/wsa:*", nsManager);

                // Delete the nodes
                foreach (XmlNode node in nodes)
                    node.ParentNode.RemoveChild(node);

                return SoapFilterResult.Continue;
            }
        }
    }
}